package yartiss.engine.simulation.system.taskset.generation;

import java.util.Arrays;
import java.util.Random;

import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.SystemMetrics;

public class UUniFastDiscardTaskSetGen implements ITaskSetGenerator {
	private final Random random = new Random();
	private final ITaskGenerator taskGenerator;
	private final int nbTask;
	private final int nbProc;
	private final double utilization;
	private final boolean offset;
	private final int deadlineModel;
	private final double energyUtilization;
	private final long pr;
	private final long Emax;
	private boolean allowEnergy;

	/**
	 * Creates a new random Taskset generator based on the UUniFast-Discard
	 * algorithm.
	 * 
	 * @param taskGen
	 *            the corresponding random task generator.
	 * @param nbTask
	 *            the number of tasks to generate.
	 * @param nbProc
	 *            the number of processor on which the task set will be
	 *            scheduled.
	 * @param utilization
	 *            the total processor utilization.
	 * @param deadlineModel
	 *            the model for the deadlines (deadlineModel < 0 for constrained
	 *            deadlines, deadlineModel == 0 for implicit deadlines and
	 *            deadlineModel > 0 for arbitrary deadlines).
	 * @param offset
	 *            true to randomly generate offset (0 <= O_i <= T_i). false
	 *            otherwise (O_i = 0).
	 */
	public UUniFastDiscardTaskSetGen(ITaskGenerator taskGen, int nbTask,
			int nbProc, double utilization, double energyUtilization, long Pr,
			long emax, int deadlineModel, boolean offset, boolean allowEnergy) {

		if (utilization > nbProc) {
			throw new IllegalArgumentException(
					"Utilization must be less than or equal to the number of processors.");
		}
		if (energyUtilization > 1) {
			throw new IllegalArgumentException(
					"Energy Utilization must be less than or equal to 1.");
		}
		this.energyUtilization = energyUtilization;
		this.taskGenerator = taskGen;
		this.nbTask = nbTask;
		this.nbProc = nbProc;
		this.utilization = utilization;
		this.offset = offset;
		this.deadlineModel = deadlineModel;
		this.Emax = emax;
		pr = Pr;
		this.allowEnergy = allowEnergy;

	}

	/**
	 * Randomly generate a Taskset using the UUniFast-Discard algorithm.
	 * 
	 * @return a task set randomly generated.
	 */

	@Override
	public ITask[] generate() {
		ITask[] taskset = new ITask[nbTask];

		if (allowEnergy) {
			ITask[] bestTasksetU = null;
			ITask[] bestTasksetUe = null;
			long tryBest = 1000;
			double U = 0, Ue = 0, bestUe = Double.MAX_VALUE, bestU = Double.MAX_VALUE;
			for (int m = 0; m < 5; m++) {
				for (long j = 0; j < tryBest; j++) {
					for (long k = 0; k < tryBest; k++) {

						// long nbTry = (tryBest*j+k);
						// long p = (nbTry*100) / (tryBest*tryBest);
						// System.out.print(c+""+nbTry+"/"+(tryBest*tryBest)+"\t"+p+"%");
						double[] util = generateUtilizations();
						double[] energyUtil = generatEnergyUtilization(util);

						for (int i = 0; i < util.length; i++) {
							do {
								taskset[i] = taskGenerator.generate(util[i],
										energyUtil[i], pr, offset,
										deadlineModel, nbProc);
							} while (((taskset[i].getWcee() / taskset[i]
									.getWcet()) - pr) > Emax);
						}
						U = SystemMetrics.utilisation(Arrays.asList(taskset));
						if (U <= 1
								&& SystemMetrics.isFPTimeFaisible(taskset)
								&& Math.abs(U - utilization) < Math.abs(bestU
										- utilization)) {
							bestTasksetU = Arrays.copyOf(taskset, nbTask);
							bestU = U;
						}
						if (U <= 1 && Math.abs(U - utilization) <= 0.05
								&& SystemMetrics.isFPTimeFaisible(taskset)
								&& SystemMetrics.isEnergyFaisible(taskset, pr))
							break;
					}

					Ue = SystemMetrics.energyUtilization(
							Arrays.asList(bestTasksetU), pr);
					if (Ue <= 1
							&& Math.abs(Ue - energyUtilization) < Math
									.abs(bestUe - energyUtilization)) {
						bestTasksetUe = Arrays.copyOf(bestTasksetU, nbTask);
						bestUe = Ue;
					}
					if (Ue <= 1 && Math.abs(Ue - energyUtilization) <= 0.05)
						break;
				}
				if (bestTasksetUe != null)
					break;
			}
			for (ITask task : bestTasksetUe) {
				long d = task.getWcee() / task.getWcet() - pr;
				assert d > 0 : "E/C-pr <=0";
				assert d <= Emax : "E/C > Emax ";
			}
			return bestTasksetUe;
		} else {
			double[] util;

			util = generateUtilizations();
			for (int i = 0; i < util.length; i++) {
				taskset[i] = taskGenerator.generate(util[i], 0, pr, offset,
						deadlineModel, nbProc);
			}
			// taskGenerator.finalizeTaskset(taskset, nbProc);
			return taskset;
		}
	}

	/**
	 * 
	 * @return
	 */
	private double[] generateUtilizations() {
		double[] util = new double[nbTask];
		double nextSumU;
		boolean discard;

		do {
			double sumU = utilization;
			discard = false;
			for (int i = 0; i < nbTask - 1; i++) {
				nextSumU = sumU
						* Math.pow(random.nextDouble(), (double) 1
								/ (nbTask - (i + 1)));
				util[i] = sumU - nextSumU;
				sumU = nextSumU;
				if (util[i] > 1) {
					discard = true;
				}
			}
			util[nbTask - 1] = sumU;
			if (util[nbTask - 1] > 1) {
				discard = true;
			}
		} while (discard || !utilizationIsValid(util));
		return util;
	}

	/**
	 * 
	 * @param util
	 * @return
	 */
	private boolean utilizationIsValid(double[] util) {
		double sum = 0;

		for (double u : util) {
			sum += u;
		}
		return sum <= nbProc ? true : false;
	}

	/**
	 * 
	 * @return
	 */
	private double[] generatEnergyUtilization(double[] procUtil) {
		double[] util = new double[nbTask];
		double nextSumU;
		boolean discard;

		do {
			double sumU = energyUtilization;
			discard = false;
			for (int i = 0; i < nbTask - 1; i++) {
				nextSumU = sumU
						* Math.pow(random.nextDouble(), (double) 1
								/ (nbTask - (i + 1)));
				util[i] = sumU - nextSumU;
				sumU = nextSumU;
				if (util[i] > 1
						|| (util[i] / procUtil[i]) > (((double) Emax) / pr + 1)) {
					discard = true;
				}
			}
			util[nbTask - 1] = sumU;
			if (util[nbTask - 1] > 1) {
				discard = true;
			}
		} while (discard || !utilizationIsValid(util));
		return util;
	}

	@Override
	public String getName() {
		return "U-Uni Fast Discard Taskset Gen";
	}

	@Override
	public String getDescription() {
		return "Creates a new random Taskset generator based on the UUniFast-Discard algorithm."; 
	}

}
